/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License. You may obtain a copy of the License at
	http://www.mozilla.org/MPL/
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import com.bourre.commands.Delegate;
import com.bourre.data.collections.Map;

import fever.app.local.Localisation;
import fever.app.local.LocalisationEvent;
import fever.app.local.LocalisationListener;
import fever.data.formatter.Formatter;
import fever.events.EventPriority;
import fever.utils.DateResources;
import fever.utils.DateUtils;

/**
 * {@code DateFormatter } class uses a pattern formated String to render date and 
 * time from a {@code Date} object. 
 * 
 * <p>If no pattern is defined, use pattern {@code "DD/MM/YYYY"} by default.
 * 
 * <p>Pattern can be defined with many terms, pre-defined are :
 * <ul>
 *   <li>D : Day in month (1 to 31)</li>
 *   <li>DD : Day in month (01 to 31)</li>
 *   <li>E : Short day name (use {@code Localisation API}</li>
 *   <li>EE : Full day name (use {@code Localisation API}</li>
 *   <li>M : Month of year (1 to 12)</li>
 *   <li>MM : Month of year (01 ti 12)</li>
 *   <li>MMM : Short month name (use {@code Localisation API}</li>
 *   <li>MMMM : Full month name (use {@code Localisation API}</li>
 *   <li>YY : Year with 2 ends digits</li>
 *   <li>YYYY : Year with 4 digits</li>
 *   <li>H : Hour in day (0 to 23)</li>
 *   <li>HH : Hour in day (00 to 23)</li>
 *   <li>L : Hour in day (0 to 11)</li>
 *   <li>LL : Hour in day (00 to 11)</li>
 *   <li>A : 'AM' or 'PM' marker</li>
 *   <li>N : Minutes in hour (0 to 59)</li>
 *   <li>NN : Minutes in hour (00 to 59)</li>
 *   <li>S : Seconds in minute (0 to 59)</li>
 *   <li>SS : Seconds in minutes (00 to 59)</li>
 * </ul>
 * 
 * <p>You can add or override pattern parsing method using {@link #addType} method.
 * 
 * <p>Example
 * {@code
 *   var f : DateFormatter = new DateFormatter();
 *   f.formatString = "EE DD MMMM YYYY - HH:NN:SS";
 *   var s : String = f.format( new Date() );
 * }
 * 
 * @author Romain Ecarnot
 */
class fever.data.formatter.DateFormatter extends Formatter implements LocalisationListener
{
	//-------------------------------------------------------------------------
	// Private properties
	//-------------------------------------------------------------------------
	
	private var _token;
	private var _m : Map;
	private var _formatString : String;
	
	
	//-------------------------------------------------------------------------
	// Public Properties
	//-------------------------------------------------------------------------
	
	/** 
	 * Formating pattern.
	 * 
	 * <p>Usually, formatting pattern is auto defined using 
	 * {@link fever.app.local.Localisation} loaded.<br />
	 * If you set the pattern using this setter, formatter no longer listen 
	 * to localisation change.
	 * 
	 * <p>Call {@link #connectToLocalisation()} method to reconnect formatter to localisation API. 
	 */
	public function get formatString() : String { return _formatString; }
	public function set formatString ( p : String ) : Void 
	{ 
		Localisation.removeLocalisationListener( this );
		_formatString = p;
	}
	
	
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 */
	public function DateFormatter()
	{
		_m = new Map();
		
		connectToLocalisation();
		
		//day
		addType( 'D', 	this, 	_parseSimpleDay	);
		addType( 'DD', 	this, 	_parseFormatDay );
		addType( 'E',	this, 	_parseShortDay	);
		addType( 'EE',	this, 	_parseFullDay	);
		
		//month
		addType( 'M', 	this, 	_parseSimpleMonth	);
		addType( 'MM', 	this, 	_parseFormatMonth	);
		addType( 'MMM',	this, 	_parseShortMonth	);
		addType( 'MMMM',this, 	_parseFullMonth		);
		
		//year
		addType( 'YY',	this, 	_parseShortYear	);
		addType( 'YYYY',this, 	_parseFullYear	);
		
		//hour
		addType( 'H',	this,	_parseSimple24Hour	);
		addType( 'HH',	this,	_parseFull24Hour	);
		addType( 'L',	this,	_parseSimple12Hour	);
		addType( 'LL',	this,	_parseFullt12Hour	);
		addType( 'A',	this,	_parseAMPM	);
		
		//minute
		addType( 'N',	this,	_parseSimpleMinute	);
		addType( 'NN',	this,	_parseFullMinute	);
		
		//second
		addType( 'S',	this,	_parseSimpleSecond	);
		addType( 'SS',	this,	_parseFullSecond	);
	}
	
	/**
	 * Defines custom formatting method for specific token.
	 * 
	 * @param type Token to search for
	 * @param scope Context of {@code f} method
	 * @param f Formatting method
	 */
	public function addType( type : String, scope, f : Function ) : Void
	{
		_m.put( type, new Delegate( scope, f ) );
	}
	
	/**
	 * Removes type to formatter
	 */
	public function removeType( type : String ) : Void
	{
		_m.remove( type );
	}
	
	/**
	 * Formats and returns passed-in {@code value}.
	 * 
	 * @return Formatted {@code String}
	 */
	public function format( value : Date ) : String
	{
		_initToken( formatString );
		return _dateToString( value );
	}
	
	/**
	 * Triggered when Localisation language change.
	 */
	public function onLocalisationUpdate( event : LocalisationEvent ) : Void
	{
		formatString = DateResources.getInstance().formatting;	
	}
	
	/**
	 * Adds current formatter as {@link fever.app.local.Localisation} listener.<br />
	 * Thus formatting pattern is autoupdate with current used lang.
	 */
	public function connectToLocalisation() : Void
	{
		Localisation.addLocalisationListener( this, EventPriority.LOCALISATION_MANAGMENT );
		formatString = DateResources.getInstance().formatting;
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	private function _initToken( pattern : String ) : Void
	{
		var char : String = '';
        var i : Number = 0;
		var len : Number = pattern.length;
		_token = new Array();
		
        while ( i < len )
        {
        	var newChar : String = pattern.charAt(i);
        	char += newChar;
			
        	if ( newChar != pattern.charAt(++i) )
        	{
				_token.push( char );
            	char = "";
        	}
        }
	}
	
	private function _dateToString(date : Date)
	{	
		var len : Number = _token.length;
		
		if( !date )
		{
			error = 'Unable to format \'undefined\' or \'null\' Date';
			return "";
		}
		
		if( len < 1 || isNaN(len) )
		{
			error = 'invalid pattern';
			return '';	
		}
		
		var result : String = '';
		
		for( var i : Number = 0; i <  len; i++)
		{
			var token : String = _token[ i ];
			if( _m.containsKey( token ) )
			{
				var d : Delegate = _m.get( token );
				d.setArguments( date );
				result += d.callFunction();
			}
			else
			{
				result += token;
			}
		}
		return result;
	}
	
	//day
	private function _parseSimpleDay( d : Date ) : String
	{
		return String( d.getDate() );
	}
	
	private function _parseFormatDay( d : Date ) : String
	{
		return _formatNumber(d.getDate());
	}
	
	private function _parseShortDay( d : Date ) : String
	{
		return DateUtils.getShortDayName( d );
	}
	
	private function _parseFullDay( d : Date ) : String
	{
		return DateUtils.getFullDayName( d );
	}
	
	//month
	private function _parseSimpleMonth( d : Date ) : String
	{
		return String( d.getMonth() + 1 );
	}
	
	private function _parseFormatMonth( d : Date ) : String
	{
		return _formatNumber( d.getMonth() + 1 );
	}
	
	private function _parseShortMonth( d : Date ) : String
	{
		return DateUtils.getShortMonthName( d );
	}
	
	private function _parseFullMonth( d : Date ) : String
	{
		return DateUtils.getFullMonthName( d );
	}
	
	//year
	private function _parseShortYear( d : Date ) : String
	{
		return DateUtils.getShortYear( d );
	}
	
	private function _parseFullYear( d : Date ) : String
	{
		return String( d.getFullYear() );
	}
	
	//hour
	private function _parseSimple24Hour( d : Date ) : String
	{
		return String( d.getHours() );
	}
	
	private function _parseFull24Hour( d : Date ) : String
	{
		return _formatNumber( d.getHours() );
	}
	
	private function _parseSimple12Hour( d : Date ) : String
	{
		return String( DateUtils.getShortHour( d ) );
	}
	
	private function _parseFullt12Hour( d : Date ) : String
	{
		return  _formatNumber( DateUtils.getShortHour( d ) );
	}
	
	private function _parseAMPM( d : Date ) : String
	{
		return (d.getHours() > 11) ? 'PM' : 'AM';
	}
	
	//minutes
	private function _parseSimpleMinute( d : Date ) : String
	{
		return String( d.getMinutes() );
	}
	
	private function _parseFullMinute( d : Date ) : String
	{
		return _formatNumber( d.getMinutes() );
	}
	
	//second
	private function _parseSimpleSecond( d : Date ) : String
	{
		return String( d.getSeconds() );
	}
	
	private function _parseFullSecond( d : Date ) : String
	{
		return _formatNumber( d.getSeconds() );
	}
	
}